package org.rsbot.script;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.rsbot.bot.Bot;
import org.rsbot.script.internal.event.ScriptListener;
import org.rsbot.script.randoms.*;
import org.rsbot.script.antiban.*;
import org.rsbot.util.GlobalConfiguration;
import org.rsbot.util.GlobalFile;
import org.rsbot.util.UncachedClassLoader;

public class ScriptHandler {
    private Bot bot;
    public static HashMap<Integer, Script> scripts = new HashMap<Integer, Script>();
    private static HashMap<Integer, Thread> scriptThreads = new HashMap<Integer, Thread>();
    private Set<ScriptListener> listeners = Collections.synchronizedSet(new HashSet<ScriptListener>());
    private volatile boolean screenshotOnFinish = false;

    public static List<UncachedClassLoader> getLoaders() {
        final List<UncachedClassLoader> loaders = new ArrayList<UncachedClassLoader>();
        final ArrayList<String> paths = new ArrayList<String>(2);
        if (!GlobalConfiguration.RUNNING_FROM_JAR) {
            final String rel = "." + File.separator + GlobalConfiguration.Paths.SCRIPTS_NAME_SRC;
            paths.add(rel);
        } else {
            /*Generate the path of the scripts folder in the jar
             *
             */
            final URL version = GlobalConfiguration.class.getClassLoader().getResource(GlobalConfiguration.Paths.Resources.VERSION);
            String p = version.toString().replace("jar:file:", "").replace(GlobalConfiguration.Paths.Resources.VERSION, GlobalConfiguration.Paths.Resources.SCRIPTS);
            try {
                p = URLDecoder.decode(p, "UTF-8");
            } catch (final UnsupportedEncodingException ignored) {
            }
            paths.add(p);
        }
        paths.add(GlobalConfiguration.Paths.getScriptsDirectory());
        paths.add(GlobalConfiguration.Paths.getScriptsPrecompiledDirectory());

        /*
         Add all jar files in the precompiled scripts directory
         *
         */
        final File psdir = new GlobalFile(GlobalConfiguration.Paths.getScriptsPrecompiledDirectory());
        if (psdir.exists()) {
            for (final File file : psdir.listFiles()) {
                if (file.getName().endsWith(".jar!")) {
                    paths.add(file.getPath());
                }
            }
        }

        for (final String path : paths) {
            try {
                final String url = new GlobalFile(path).toURI().toURL().toString();
                loaders.add(new UncachedClassLoader(url, UncachedClassLoader.class.getClassLoader()));
            } catch (final MalformedURLException e) {
                e.printStackTrace();
            }
        }
        return loaders;
    }

    public ScriptHandler(Bot bot) {
        this.bot = bot;
    }

    private final ArrayList<org.rsbot.script.Random> randoms = new ArrayList<org.rsbot.script.Random>();
    private final ArrayList<org.rsbot.script.Antiban> antiban = new ArrayList<org.rsbot.script.Antiban>();

    public void addRandoms() {
        try {
            randoms.add(new BeehiveSolver());
            randoms.add(new CapnArnav());
            randoms.add(new Certer());
            randoms.add(new DrillDemon());
            randoms.add(new FreakyForester());
            randoms.add(new FrogCave());
            randoms.add(new GraveDigger());
            randoms.add(new LostAndFound());
            randoms.add(new Maze());
            randoms.add(new Mime());
            randoms.add(new Molly());
            randoms.add(new Exam());
            randoms.add(new Pillory());
            randoms.add(new Pinball());
            randoms.add(new Prison());
            randoms.add(new QuizSolver());
            randoms.add(new SandwhichLady());
            randoms.add(new ScapeRuneIsland());
            randoms.add(new FirstTimeDeath());
            randoms.add(new LeaveSafeArea());
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }

    public void addAntiban() {
        try{
            antiban.add(new BankPins());
            antiban.add(new BreakHandler());
            antiban.add(new ImprovedRewardsBox());
            antiban.add(new InterfaceCloser());
            antiban.add(new LoginBot());
            antiban.add(new SystemUpdate());
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }

    public void addScriptListener(ScriptListener l) {
		listeners.add(l);
	}

	public void removeScriptListener(ScriptListener l) {
		listeners.remove(l);
	}

    private void addScriptToPool(final Script ss, final Thread t) {
        for (int off = 0; off < scripts.size(); off++) {
            if (!scripts.containsKey(off)) {
                scripts.put(off, ss);
                ss.setID(off);
                scriptThreads.put(off, t);
                return;
            }
        }
        ss.setID(scripts.size());
        scripts.put(scripts.size(), ss);
        scriptThreads.put(scriptThreads.size(), t);
    }

    public Bot getBot() {
		return bot;
	}

    public Collection<org.rsbot.script.Random> getRandoms() {
        return randoms;
    }

    public Collection<org.rsbot.script.Antiban> getAntiban() {
        return antiban;
    }

    public Map<Integer, Script> getRunningScripts() {
        return Collections.unmodifiableMap(scripts);
    }

    public void pauseScript(final int id) {
        Script s = scripts.get(id);
        s.setPaused(!s.isPaused());
        if (s.isPaused()) {
            for (ScriptListener l : listeners) {
                l.scriptPaused(this, s);
            }
        } else {
            for (ScriptListener l : listeners) {
                l.scriptResumed(this, s);
            }
        }

    }

    public void stopScript(int id) {
        stopScript(id, false);
    }
    
    /**
     * @param id Id of the script to stop
     * @param forceStop Interrupt script thread
     */
    public void stopScript(int id, boolean forceStop) {
        Script script = scripts.get(id);
        if (script != null) {
            script.deactivate(id);
            scripts.remove(id);
            
            Thread t = scriptThreads.get(id);
            if (t != null && forceStop)
            	t.interrupt();
            
            scriptThreads.remove(id);
            for (ScriptListener l : listeners) {
                l.scriptStopped(this, script);
            }
        }
    }

    public void runScript(final Script script, final Map<String, String> map) {
        script.init(bot.getMethods());
        
		for (ScriptListener l : listeners) {
			l.scriptStarted(this, script);
		}
		
		String tName = "";
		
		if (script.getClass().isAnnotationPresent(ScriptManifest.class))
			tName = script.getClass().getAnnotation(ScriptManifest.class).name();
		else
			tName = script.getClass().getName();

        final Thread t = new Thread(new Runnable() {
            public void run() {
                script.run(map);
            }
        }, "Script-" + tName);

        addScriptToPool(script, t);
        t.start();
    }

    public void stopAllScripts() {
		for (int i : scripts.keySet()) {
			stopScript(i);
		}
	}

    public void stopScript() {
		Thread curThread = Thread.currentThread();
		for (int i = 0; i < scripts.size(); i++) {
			Script script = scripts.get(i);
			if (script != null && script.isRunning()) {
				if (scriptThreads.get(i) == curThread) {
					stopScript(i);
				}
			}
		}
		if (curThread == null) {
			throw new ThreadDeath();
		}
	}

    public void stopScriptAntiRand(boolean activateRandom) {
        Thread curThread = Thread.currentThread();
		for (int i = 0; i < scripts.size(); i++) {
			Script script = scripts.get(i);
			if (script != null && script.isRunning()) {
				if (scriptThreads.get(i) == curThread) {
					stopScript(i);
				}
			}
		}
                Bot.disableRandoms = activateRandom;
		if (curThread == null) {
			throw new ThreadDeath();
		}
	}

    public void stopScriptAntiBan(boolean activateAntiban) {
        Thread curThread = Thread.currentThread();
		for (int i = 0; i < scripts.size(); i++) {
			Script script = scripts.get(i);
			if (script != null && script.isRunning()) {
				if (scriptThreads.get(i) == curThread) {
					stopScript(i);
				}
			}
		}
                Bot.disableAntibans = activateAntiban;
		if (curThread == null) {
			throw new ThreadDeath();
		}
	}

    public void setScreenshotOnFinish(boolean screenshotOnFinish) {
        this.screenshotOnFinish = screenshotOnFinish;
    }

    public boolean isScreenshotOnFinish() {
        return screenshotOnFinish;
    }

    public void updateInput(final Bot bot, final int mask) {
		for (final ScriptListener l : listeners) {
			l.inputChanged(bot, mask);
		}
	}
}